import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib.font_manager import FontProperties

# 设置全局字体配置
plt.rcParams['font.size'] = 10
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['text.usetex'] = False

# 创建支持中文的字体属性
chinese_font = FontProperties(fname=mpl.font_manager.findfont('SimHei'))

# 创建图形
fig, ax = plt.subplots(figsize=(8, 6), dpi=100)

# ================== 数据准备 ==================
# 预测误差区间 (单位：%)
error_bins = np.array([0, 5, 10, 15, 20, 25, 30])

# 各误差区间对应的额外准确率损失 (单位：%)
# 根据文档描述：预测误差<5%时额外损失仅0.8%（远低于固定噪声4.2%）
loss_values = {
    'GP-AdaFL': np.array([0.8, 1.2, 1.8, 2.5, 3.5, 5.0]),  # 本文方案
    'DP-FedAvg': np.array([4.2, 5.0, 6.0, 7.5, 9.0, 11.0]),  # 基准方案
    'DP-FedANAW': np.array([2.5, 3.0, 4.0, 5.5, 7.0, 9.0])   # 对比方案
}

# 误差区间中点位置 (用于绘图)
mid_points = (error_bins[:-1] + error_bins[1:]) / 2

# ================== 绘制曲线 ==================
# 绘制GP-AdaFL曲线（本文方案）
line1, = ax.plot(mid_points, loss_values['GP-AdaFL'], 
                 'o-', color='#1f77b4', linewidth=2, markersize=8,
                 label='GP-AdaFL (本文方案)')

# 绘制DP-FedAvg曲线（基准方案）
line2, = ax.plot(mid_points, loss_values['DP-FedAvg'], 
                 's--', color='#ff7f0e', linewidth=2, markersize=8,
                 label='DP-FedAvg (基准方案)')

# 绘制DP-FedANAW曲线（对比方案）
line3, = ax.plot(mid_points, loss_values['DP-FedANAW'], 
                 'D-.', color='#2ca02c', linewidth=2, markersize=8,
                 label='DP-FedANAW (对比方案)')

# ================== 关键区域标注 ==================
# 标注低误差区域优势
ax.annotate('预测误差<5%时:\nGP-AdaFL损失仅0.8%\nDP-FedAvg损失4.2%', 
            xy=(2.5, 1.0), xytext=(10, 3.0),
            arrowprops=dict(arrowstyle='->', color='dimgray', connectionstyle="arc3,rad=-0.2"),
            fontsize=10, fontproperties=chinese_font,
            bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="#1f77b4", alpha=0.8))

# 标注高误差区域
ax.annotate('误差>20%时:\nGP-AdaFL损失仍低于DP-FedAvg 60%', 
            xy=(25, 4.5), xytext=(15, 8.0),
            arrowprops=dict(arrowstyle='->', color='dimgray'),
            fontsize=10, fontproperties=chinese_font,
            bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="#ff7f0e", alpha=0.8))

# ================== 设置图形属性 ==================
# 设置坐标轴范围
ax.set_xlim(0, 30)
ax.set_ylim(0, 12)

# 添加标题和标签
ax.set_title('图6：预测误差对模型准确率的影响', 
             fontsize=14, fontweight='bold', fontproperties=chinese_font)
ax.set_xlabel('预测误差区间 (%)', fontsize=12, fontproperties=chinese_font)
ax.set_ylabel('额外准确率损失 (%)', fontsize=12, fontproperties=chinese_font)

# 添加网格
ax.grid(True, linestyle='--', alpha=0.7)

# 添加图例
ax.legend(loc='upper left', prop=chinese_font, frameon=True, framealpha=0.9)

# 添加关键结论标注
ax.text(0.5, -0.15, '关键结论: GP-AdaFL在预测误差<5%时额外损失仅0.8%(固定噪声方案4.2%)，误差>20%时损失仍低60%', 
        transform=ax.transAxes, ha='center', fontsize=11, 
        fontproperties=chinese_font, 
        bbox=dict(boxstyle="round,pad=0.3", fc="#f0f0f0", ec="black", alpha=0.8))

# ================== 添加数据标签 ==================
# 为GP-AdaFL添加数据标签
for i, txt in enumerate(loss_values['GP-AdaFL']):
    ax.annotate(f'{txt:.1f}%', 
                (mid_points[i], loss_values['GP-AdaFL'][i]),
                textcoords="offset points", 
                xytext=(0,10), 
                ha='center',
                fontsize=9,
                bbox=dict(boxstyle="round,pad=0.2", fc="#1f77b4", alpha=0.2))

# 为DP-FedAvg添加数据标签
for i, txt in enumerate(loss_values['DP-FedAvg']):
    ax.annotate(f'{txt:.1f}%', 
                (mid_points[i], loss_values['DP-FedAvg'][i]),
                textcoords="offset points", 
                xytext=(0,-15), 
                ha='center',
                fontsize=9,
                bbox=dict(boxstyle="round,pad=0.2", fc="#ff7f0e", alpha=0.2))

# ================== 添加技术标注 ==================
fig.text(0.5, 0.01, 
         "实验设置: ε=5 | 数据集: CIFAR-10 | 模型: ResNet-18 | 预测窗口大小: 5轮", 
         ha="center", fontsize=10, style='italic', fontproperties=chinese_font)

# 调整布局并保存
plt.tight_layout(rect=[0, 0.05, 1, 0.95])
plt.savefig('prediction_error_impact.png', bbox_inches='tight', dpi=300)
plt.show()
